package net.chowda.castcluster.provider;

import com.sun.syndication.feed.module.Module;
import com.sun.syndication.feed.module.mediarss.MediaEntryModuleImpl;
import com.sun.syndication.feed.module.mediarss.types.MediaContent;
import com.sun.syndication.feed.module.mediarss.types.MediaGroup;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndEnclosure;
import com.sun.syndication.fetcher.FeedFetcher;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
import net.chowda.castcluster.CastVideoItem;
import net.chowda.castcluster.Video;
import net.chowda.castcluster.VideoSource;
import net.chowda.castcluster.util.Base64Util;
import net.chowda.castcluster.util.CastClusterConfig;
import net.chowda.castcluster.util.LogUtil;
import net.chowda.castcluster.util.MovieUtil;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;

/**
 * handles google videos....
 * <p/>
 * url format:
 * gv|video|-8219255750387944214
 *
 * this format is for videos which don't supply an mp4 version.. we will have to fetch the FLV file and convert it.
 *
 * users should need to worry about this.
 *
 * gv|flv|-8219255750387944214
 *
 * <p/>
 */
public class GoogleVideoProvider extends WebFeedProvider {
    private static final Logger LOG = LogUtil.getLogger(GoogleVideoProvider.class);
    private static final String VIDEO_RSS_URL = "http://video.google.com/videofeed?fgvns=1&fai=1&hl=en&docid=";
    private static final String FLV_FETCH_URL = "http://video.google.com/videofile/blah.flv?itag=5&docid=";

    /**
     * fetch google flash and convert to mp4.
     *
     * this means the video does not provide us with an mp4 version... we'll make one ourselves!
     */
    public String fetchProviderVideo(String videoUrl) {
        String videoId = videoUrl.split("\\|")[2];

        // make sure video dir exists.
        String destDir = CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR) + File.separator + "video";
        new File(destDir).mkdirs();

        String sourceFile = destDir + File.separator + videoId + ".flv";
        String destFile = getVideoPath(videoId);

        if (!new File(destFile).exists()) {
            GetMethod method = new GetMethod(FLV_FETCH_URL + videoId);
            HttpClient client = new HttpClient();
            try {
                LOG.info("downloading google video: " + videoId);
                client.executeMethod(method);
            } catch (IOException e) {
                LOG.error("could not fetch google video!", e);
                throw new RuntimeException(e);
            }
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(sourceFile);
                BufferedInputStream is = new BufferedInputStream(method.getResponseBodyAsStream());

                byte[] b = new byte[2024];
                int count;
                while((count = is.read(b)) > -1) {
                    fileOutputStream.write(b, 0, count);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            } catch (IOException e) {
                LOG.error("could not write google video to disk!", e);
                throw new RuntimeException(e);
            }

            MovieUtil.convertVideoToMPEG4(sourceFile, destFile);
        } else {
            LOG.info("video " + videoId + " already exists... no need to fetch or convert, yay!");
        }
        return destFile;
    }

    /**
     * clean up after any video conversion that was done... temp download files and converted files.
     *
     * this method is only called after a fetch has been done by some client.
     */
    public void providerCleanupAfterFetch(String ccUrl) {
        String videoId = ccUrl.split("\\|")[2];
        LOG.info("cleanup up fetched file and converted file for: " + ccUrl);

        String destDir = CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR) + File.separator + "video";
        String source = destDir + File.separator + videoId + ".flv";
        String dest = getVideoPath(videoId);

        File sourceFile = new File(source);
        if(sourceFile.exists()) sourceFile.delete();
        File destFile = new File(dest);
        if(destFile.exists()) destFile.delete();
    }

    /**
     * this gets the path to where the *converted* video _should_ be.
     *
     * @videoId the google videoId (a.k.a. docId)
     */
    public String getVideoPath(String videoId) {
        return CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR) + File.separator + "video" + File.separator + videoId + ".mp4";
    }

    public List<Video> getProviderVideos(String url) {
        return getVideosFromRss(url.split("\\|")[2]);
    }

    protected List<Video> getVideosFromRss(String videoId) {
        List<Video> videos = new ArrayList<Video>();
        // this means there are no podcast enclosures... we'll attempt to treat it like a yahoo mediarss feed
        // this is kinda specific to google video... these videos will need to be fetched and converted.

        // turn these feed items into our generic video items for sorting and later reconsitution.
        SyndFeed syndFeed = fetchFeed(videoId);
        // iterate over all the entries in this feed.
        for (Object entry : syndFeed.getEntries()) {
            SyndEntryImpl syndEntry = (SyndEntryImpl) entry;

            // if the entry contains "enclosures" it's probably a podcast entry, and we'll use that data.
            List list = syndEntry.getEnclosures();
            if(list.size() > 0) {
                // even though we're looping.. the end result should be a single new video item from this entry.
                for (Object o : list) {
                    SyndEnclosure enc = (SyndEnclosure) o;

                    CastVideoItem item = new CastVideoItem();
                    item.setLengthInSeconds((int) enc.getLength());
                    item.setUrl(enc.getUrl());
                    item.setVidSize(enc.getLength());
                    item.setAuthor(syndEntry.getAuthor());
                    item.setId(syndEntry.getUri());
                    item.setThumbnailUrl("");
                    item.setTimeUploaded(syndEntry.getPublishedDate());
                    item.setTitle(syndEntry.getTitle());

                    SyndContent description = syndEntry.getDescription();
                    if (description != null && description.getValue() != null && description.getValue().length() > 0) {
                        item.setDescription(description.getValue());
                    }
                    videos.add(item);
                }
            } else {
                // if there are no enclosures, it is likely that this entry is for a google video
                // that doesn't have an mp4 file.. we will attempt to find the FLV version of the file
                // using the yahoo mediarss modules.
                Module module = syndEntry.getModule("http://search.yahoo.com/mrss/");
                MediaEntryModuleImpl me = (MediaEntryModuleImpl) module;
                // even though we're looping.. the end result should be a single new video item from this entry.
                for (MediaGroup mediaGroup : me.getMediaGroups()) {
                    for (MediaContent mediaContent : mediaGroup.getContents()) {
                        if ("video/x-flv".equals(mediaContent.getType())) {
                            // ok, this is what we want..
                            CastVideoItem item = new CastVideoItem();
                            item.setLengthInSeconds(mediaContent.getDuration().intValue());
                            // set a url for this video that will hit the MovieServlet and force conversion.
                            item.setUrl(CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.BASE_URL) + "/PlayList.mp4?videoUrl=" + Base64Util.encode("gv|flv|" + videoId));
                            item.setVidSize(0);
                            item.setVidType(mediaContent.getType());
                            item.setAuthor(syndEntry.getAuthor());
                            item.setId(syndEntry.getUri());
                            item.setTimeUploaded(syndEntry.getPublishedDate());
                            item.setTitle(syndEntry.getTitle());

                            SyndContent description = syndEntry.getDescription();
                            if (description != null && description.getValue() != null && description.getValue().length() > 0) {
                                item.setDescription(description.getValue());
                            }
                            videos.add(item);
                        }
                    }

                }
            }
        }
        return videos;
    }

    public VideoSource getProviderSourceForCCUrl(String url) {
        LOG.info("fetching google video feed: " + url);
        VideoSource source = new VideoSource();
        source.setCcUrl(url);
        source.setUrl("http://video.google.com/videoplay?docid=" + url.split("\\|")[2]);
        SyndFeed syndFeed = fetchFeed(url.split("\\|")[2]);
        source.setName(syndFeed.getTitle());
        source.setVideos(getProviderVideos(url));
        LOG.info("done fetching google video feed: " + url);
        return source;
    }

    protected SyndFeed fetchFeed(String videoId) {
        // this feed fetcher business knows how to handle gzipped feeds.. thank sweet baby jesus.
        FeedFetcher feedFetcher = new HttpURLFeedFetcher(feedInfoCache);
        try {
            return feedFetcher.retrieveFeed(new URL(VIDEO_RSS_URL + videoId));
        } catch (Exception e) {
            throw new RuntimeException("couldn't fetch google video feed: " + VIDEO_RSS_URL + videoId, e);
        }
    }
}
